﻿using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Security.Cryptography;

namespace Framework.Common.ConsistentHash
{
    /// <summary>
    /// Implements an FNV hash algorithm.
    /// </summary>
    public static class HashFactory
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static uint Calculate(string data)
        {
            return FNVHash.Hash(data);
        }

        /// <summary>
        /// Gets the MD5 hash value for the passed in value parameter
        /// </summary>
        /// <param name="value">The string value to hash</param>
        /// <returns>The MD5 hash of the value parameter</returns>
        public static uint Md5Hash(string value)
        {
            var md5ServiceProdivder = new MD5CryptoServiceProvider();
            byte[] data = Encoding.ASCII.GetBytes(value);
            data = md5ServiceProdivder.ComputeHash(data);
            return BitConverter.ToUInt32(data, 0);
        }
    }

    public class FNVHash
    {
        private const uint Prime = 16777619;
        private const uint Offset = 2166136261;

        /// <summary>
        /// Compute the hash value for a specified data.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static uint Hash(string data)
        {
            uint hashvalue = Offset;
            byte[] array = Encoding.ASCII.GetBytes(data);

            int end = array.Length;
            for (int i = 0; i < end; i++)
            {
                hashvalue = (hashvalue ^ array[i]) * Prime;
            }
            hashvalue += hashvalue << 13;
            hashvalue ^= hashvalue >> 7;
            hashvalue += hashvalue << 3;
            hashvalue ^= hashvalue >> 17;
            hashvalue += hashvalue << 5;
            return hashvalue;
        }
    }

    public class MurmurHash2
    {
        const UInt32 M = 0x5bd1e995;
        const Int32 R = 24;

        [StructLayout(LayoutKind.Explicit)]
        struct BytetoUInt32Converter
        {
            [FieldOffset(0)]
            public Byte[] Bytes;

            [FieldOffset(0)]
            public UInt32[] UInts;
        }

        public static UInt32 Hash(string data)
        {
            var bytes = Encoding.ASCII.GetBytes(data);
            return Hash(bytes, 0xc58f1a7b);
        }

        public static UInt32 Hash(Byte[] data, UInt32 seed)
        {
            Int32 length = data.Length;
            if (length == 0)
                return 0;
            UInt32 h = seed ^ (UInt32)length;
            Int32 currentIndex = 0;
            // array will be length of Bytes but contains Uints
            // therefore the currentIndex will jump with +1 while length will jump with +4
            UInt32[] hackArray = new BytetoUInt32Converter { Bytes = data }.UInts;
            while (length >= 4)
            {
                UInt32 k = hackArray[currentIndex++];
                k *= M;
                k ^= k >> R;
                k *= M;

                h *= M;
                h ^= k;
                length -= 4;
            }
            currentIndex *= 4; // fix the length
            switch (length)
            {
                case 3:
                    h ^= (UInt16)(data[currentIndex++] | data[currentIndex++] << 8);
                    h ^= (UInt32)data[currentIndex] << 16;
                    h *= M;
                    break;
                case 2:
                    h ^= (UInt16)(data[currentIndex++] | data[currentIndex] << 8);
                    h *= M;
                    break;
                case 1:
                    h ^= data[currentIndex];
                    h *= M;
                    break;
                default:
                    break;
            }

            // Do a few final mixes of the hash to ensure the last few
            // bytes are well-incorporated.

            h ^= h >> 13;
            h *= M;
            h ^= h >> 15;

            return h;
        }
    }
}